package com.flyan.game.motafx.core.orb;

import com.flyan.game.motafx.component.character.BraveComponent;
import com.flyan.game.motafx.core.consts.PropertyConsts;
import com.flyan.game.motafx.core.map.FloorMap;
import com.flyan.game.motafx.core.map.TowerMap;
import com.flyan.game.motafx.core.pojo.LifePanel;
import com.flyan.game.motafx.core.util.entity.EntityUtils;
import com.flyan.game.motafx.entity.monster.MonsterInfo;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 勇者灵球（怪物手册）
 *
 * @author flyan
 * @version 1.0
 * @email flyan_by_chenu@sina.com
 * @since 2025/2/8
 */
@RequiredArgsConstructor
public class BraveOrb {

    /**
     * 计算近几次临界值
     */
    private static final int NEXT_CRITICAL_VALUES_COUNT = 3;

    /**
     * 魔塔地图
     */
    private final TowerMap tower;

    /**
     * 勇者组件信息
     */
    private final BraveComponent brave;

    /**
     * 当前楼层怪物伤害预测
     *
     * @return 预测信息
     */
    public List<Prediction> currentFloorPredication() {
        return floorPredication(tower.getCurrentFloor());
    }

    /**
     * 楼层怪物伤害预测
     *
     * @param floor 楼层
     * @return 预测信息
     */
    public List<Prediction> floorPredication(FloorMap floor) {
        return floor.getMonsterInfos().stream()
                .map(it -> monsterPredication(it, true))
                .sorted(Comparator.comparingInt(Prediction::getDamage))
                .collect(Collectors.toList());
    }

    /**
     * 怪物伤害预测
     *
     * @param monsterInfo 怪物信息
     * @param firstAttack 勇士是否先攻，否则表示怪物先攻
     * @return 预测信息
     */
    public Prediction monsterPredication(MonsterInfo monsterInfo, boolean firstAttack) {
        LifePanel braveLifePanel = EntityUtils.getEntityProperty(brave.getEntity(), PropertyConsts.LIFE_PANEL, LifePanel.class);
        LifePanel monsterLifePanel = monsterInfo.getLifePanel();

        Prediction pre = new Prediction();
        pre.monsterInfo = monsterInfo;

        pre.lossHits = (Math.max(monsterLifePanel.getAck() - braveLifePanel.getDef(), 0));
        if (braveLifePanel.getAck() <= monsterLifePanel.getDef()) { // 未破防
            pre.setNumberOfHits(Integer.MAX_VALUE);
            pre.setDamage(Integer.MAX_VALUE);
        } else { // 已破防
            int braveHitDamage = braveLifePanel.getAck() - monsterLifePanel.getDef();
            int round = braveHitDamage >= monsterLifePanel.getHp()
                    ? 0 // 一下打死，不会受击
                    : monsterLifePanel.getHp() / braveHitDamage;
            if (round > 0 &&monsterLifePanel.getHp() % braveHitDamage == 0) {
                round -= 1; // 最后一击刚好打死怪物，不会受击
            }
            if (!firstAttack) {
                round += 1; // 怪物先攻，还需要多承受一回合伤害
            }
            pre.numberOfHits = round;
            pre.damage = pre.lossHits * pre.numberOfHits;
        }

        /* 若还未无伤，计算近几次的临界值 */
        if (pre.damage > 0) {
            pre.nextCriticalValues = new ArrayList<>(NEXT_CRITICAL_VALUES_COUNT);
            int atk = braveLifePanel.getAck();
            int def = monsterLifePanel.getDef();
            if (atk <= def) { // 未破防，那么可预见的下一临界值就是破防！
                atk = def + 1;
                pre.nextCriticalValues.add(atk);
            }

            /* 以现在的攻击力初始化一次受击回合数 */
            int braveHitDamage = atk - def;
            int lastRound = monsterLifePanel.getHp() / braveHitDamage;

            /* 持续找到这些临界值 */
            atk += 1;
            while (pre.nextCriticalValues.size() < NEXT_CRITICAL_VALUES_COUNT && lastRound > 0) {
                braveHitDamage = atk - def;
                int round = braveHitDamage >= monsterLifePanel.getHp()
                        ? 0 // 一下打死，不会受击
                        : monsterLifePanel.getHp() / braveHitDamage;
                if (round > 0 && monsterLifePanel.getHp() % braveHitDamage == 0) {
                    round -= 1; // 最后一击刚好打死怪物，不会受击
                }
                if (round < lastRound) { // 受击回合数减 1，找到一个临界点
                    pre.nextCriticalValues.add(atk);
                    lastRound = round;
                }
                atk += 1;
            }
        } else {
            pre.nextCriticalValues = Collections.emptyList();
        }

        return pre;
    }

    public void printPrediction(List<Prediction> predictions) {
        System.out.println("NAME\t\tHP\tATK\tDEF\tGold\tLoss/Hits\t# of Hits\tDamage\tNext Critical Values");
        System.out.println("-----------------------------------------------------------------------------------------");
        for (Prediction prediction : predictions) {
            System.out.println(prediction.toString());
        }
        System.out.println();
    }

    /**
     * 预测信息
     */
    @Data
    public static class Prediction {

        /**
         * 怪物生命面板
         */
        private MonsterInfo monsterInfo;

        /**
         * Loss/Hits 受击回合伤害
         */
        private int lossHits;

        /**
         * # of Hits 受击回合数，即 1 防减伤值
         */
        private int numberOfHits;

        /**
         * 总伤害
         */
        private int damage;

        /**
         * 近几次的临界值
         */
        private List<Integer> nextCriticalValues;

        @Override
        public String toString() {
            LifePanel lifePanel = monsterInfo.getLifePanel();
            return "%s\t\t%d\t%d\t%d\t%d\t\t%d\t\t\t%s\t\t\t%s\t\t\t%s\t%s\t%s".formatted(
                    monsterInfo.getName(),
                    lifePanel.getHp(),
                    lifePanel.getAck(),
                    lifePanel.getDef(),
                    lifePanel.getGold(),
                    lossHits,
                    numberOfHits != Integer.MAX_VALUE ? numberOfHits + "" : "-",
                    damage != Integer.MAX_VALUE ? damage + "" : "-",
                    nextCriticalValues.size() > 0 ? nextCriticalValues.get(0).toString() : "-",
                    nextCriticalValues.size() > 1 ? nextCriticalValues.get(1).toString() : "-",
                    nextCriticalValues.size() > 2 ? nextCriticalValues.get(2).toString() : "-"
            );
        }
    }

}
